// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

/**

Dataset dump for OrderLatency Model

1. dataset1 (sampled at order submit)
submit_ts X1 X2 ... Xn     Y1 (SUBMIT_TO_CONFIRM)  Y@ (SUBMIT_TO_FIRST_FILL)

2. dataset2 (sampled at order cancel submit)
cancel_submit_ts X1 X2 ... Xn     Y1 (CANCEL_TO_CONFIRM)
**/

#pragma once

#include <experimental/filesystem>
#include <string>
#include <unordered_map>
#include <vector>

#include <highfive/H5DataSet.hpp>
#include <highfive/H5DataSpace.hpp>
#include <highfive/H5File.hpp>

#include "coin/proto/coin_order_gateway.pb.h"
#include "coin/proto/coin_order_enums.pb.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/strategy/strategy.h"
#include "coin2/support/order_latency/feature.h"
#include "presto/quant/math/moving_average.h"
#include "presto/quant/math/moving_window.h"

using coin::proto::OrderEvent;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::order::OrderUpdate;
using presto::math::MovingWindowWithCallee;
// using presto::math::TimeWindowMovingAverage;

struct PendingRow {
  int row;  // means ys[row] should be populated
  int window_size;
};

enum Y {
  SUBMIT_TO_CONFIRM,
  SUBMIT_TO_FIRST_FILL,
  CANCEL_SUBMIT_TO_CONFIRM,
  //
  NUM_Y
};

struct OrderStatus {
  int dataset1_row = 0;
  int dataset2_row = 0;

  int64_t submit_ts = 0;
  int64_t submit_ack_ts = 0;
  int64_t submit_confirm_ts = 0;
  int64_t first_fill_ts = 0;
  int64_t cancel_submit_ts = 0;
  int64_t cancel_confirm_ts = 0;
  bool dirty = false;  // e.g. mixed order of CANCEL_SUBMIT => ERROR => SUBMIT => CONFIRM
};

class LatencyModelDatasetDumper : public coin2::strategy::IStrategy {
 public:
  explicit LatencyModelDatasetDumper(const std::string& target_symbol)
      : target_symbol_(target_symbol), features_(target_symbol) {
    // order_submit_to_ack_time_10ms_.ResizeWindow(10 * MS);
    // order_submit_to_ack_time_20ms_.ResizeWindow(20 * MS);
    // order_submit_to_ack_time_30ms_.ResizeWindow(30 * MS);

    // order_submit_to_first_fill_time_10ms_.ResizeWindow(10 * MS);
    // order_submit_to_first_fill_time_20ms_.ResizeWindow(20 * MS);
    // order_submit_to_first_fill_time_30ms_.ResizeWindow(30 * MS);

    // order_cancel_submit_to_confirm_time_10ms_.ResizeWindow(10 * MS);
    // order_cancel_submit_to_confirm_time_20ms_.ResizeWindow(20 * MS);
    // order_cancel_submit_to_confirm_time_30ms_.ResizeWindow(30 * MS);
  }

  void onTradeFeed(const FeedUpdate& upd) override {
    if (upd.product().absolute_norm() != target_symbol_) {
      return;
    }
    features_.onTradeFeed(upd);
  }

  // e.g. dataset1 = "out/dataset_order_submit.h5"
  // e.g. dataset2 = "out/dataset_order_cancel.h5"
  void SaveToFiles(
      const std::experimental::filesystem::path& dataset1,
      const std::experimental::filesystem::path& dataset2) {
    using namespace HighFive;

    LOG(INFO) << "Saving " << dataset1_ts_.size() << " row(s) to " << dataset1;
    LOG(INFO) << "Saving " << dataset2_ts_.size() << " row(s) to " << dataset2;

    std::experimental::filesystem::create_directories(dataset1.parent_path());
    std::experimental::filesystem::create_directories(dataset2.parent_path());
    {
      std::vector<size_t> dims = {dataset1_ts_.size(), Feature::NUM_FEATURE};

      File file(dataset1, File::ReadWrite | File::Create | File::Truncate);
      DataSet ts = file.createDataSet<int64_t>("/ts", DataSpace::From(dataset1_ts_));
      DataSet x = file.createDataSet<double>("/x", DataSpace(dims));
      DataSet y1 = file.createDataSet<double>("/y1", DataSpace::From(dataset1_y1_));
      DataSet y2 = file.createDataSet<double>("/y2", DataSpace::From(dataset1_y2_));
      ts.write(dataset1_ts_);
      x.write_raw(dataset1_x_.data());
      y1.write(dataset1_y1_);
      y2.write(dataset1_y2_);
    }
    {
      std::vector<size_t> dims = {dataset2_ts_.size(), Feature::NUM_FEATURE};

      File file(dataset2, File::ReadWrite | File::Create | File::Truncate);
      DataSet ts = file.createDataSet<int64_t>("/ts", DataSpace::From(dataset2_ts_));
      DataSet x = file.createDataSet<double>("/x", DataSpace(dims));
      DataSet y = file.createDataSet<double>("/y", DataSpace::From(dataset2_y_));
      ts.write(dataset2_ts_);
      x.write_raw(dataset2_x_.data());
      y.write(dataset2_y_);
    }
  }

  void onAccountOrder(const OrderUpdate& upd) override {
    auto& event = upd.product_order_info().event();
    if (event.symbol() != target_symbol_) {
      return;
    }
    bool found = orders_.find(event.proc_order_id()) != orders_.end();

    if (event.type() == OrderEvent::ORDER_SUBMITTED) {
      if (found) {
        orders_[event.proc_order_id()].dirty = true;
      } else {
        // new order registered
        OrderStatus os;
        os.submit_ts = upd.timestamp();
        os.dataset1_row = dataset1_ts_.size();
        orders_[event.proc_order_id()] = os;

        // record x
        dataset1_ts_.push_back(upd.timestamp());
        dataset1_x_.resize(dataset1_ts_.size() * Feature::NUM_FEATURE);
        dataset1_y1_.push_back(-1.0);  // placeholder
        dataset1_y2_.push_back(-1.0);  // placeholder
        nonstd::span<double, Feature::NUM_FEATURE> row(
            &dataset1_x_[(dataset1_ts_.size() - 1) * Feature::NUM_FEATURE],
            Feature::NUM_FEATURE);
        features_.GetX(row);
      }
    } else {
      if (!found) {
        // give up this order
        OrderStatus os;
        os.dirty = true;
        orders_[event.proc_order_id()] = os;
        return;
      }
      auto& s = orders_[event.proc_order_id()];
      if (s.dirty) {
        return;
      }
      if (event.type() == OrderEvent::ORDER_ACCEPTED) {
        if (s.submit_confirm_ts == 0) {
          s.submit_confirm_ts = upd.timestamp();
          // y1
          dataset1_y1_[s.dataset1_row] = s.submit_confirm_ts - s.submit_ts;
        } else {
          s.dirty = true;
          LOG(ERROR) << "OrderSubmit is confirmed twice";
        }
      } else if (event.type() == OrderEvent::ORDER_FILLED) {
        if (s.first_fill_ts == 0) {
          s.first_fill_ts = upd.timestamp();
          // y2
          if (event.fill_type() == coin::proto::FillType::TAKER_FILL_TYPE) {
            dataset1_y2_[s.dataset1_row] = s.first_fill_ts - s.submit_ts;
          }
        }
      } else if (event.type() == OrderEvent::CANCEL_SUBMITTED) {
        if (s.cancel_submit_ts == 0) {
          s.cancel_submit_ts = upd.timestamp();
          // new order cancel registered
          s.dataset2_row = dataset2_ts_.size();

          // record x
          dataset2_ts_.push_back(upd.timestamp());
          dataset2_x_.resize(dataset2_ts_.size() * Feature::NUM_FEATURE);
          dataset2_y_.push_back(-1.0);  // placeholder
          nonstd::span<double, Feature::NUM_FEATURE> row(
              &dataset2_x_[(dataset2_ts_.size() - 1) * Feature::NUM_FEATURE],
              Feature::NUM_FEATURE);
          features_.GetX(row);
        } else {
          s.dirty = true;
          LOG(ERROR) << "OrderCancel is submitted twice";
        }
      } else if (
          event.type() == OrderEvent::CANCEL_ERROR || event.type() == OrderEvent::CANCEL_REJECTED) {
        if (s.cancel_confirm_ts == 0) {
          s.cancel_submit_ts = 0;
        }
      } else if (event.type() == OrderEvent::CANCEL_CONFIRMED) {
        if (s.cancel_confirm_ts == 0) {
          s.cancel_confirm_ts = upd.timestamp();
          dataset2_y_[s.dataset2_row] = upd.timestamp() - s.submit_ts;
        } else {
          s.dirty = true;
          LOG(ERROR) << "OrderCancel is confirmed twice";
        }
      }
    }
  }

 private:
  std::string target_symbol_;

  static constexpr const int64_t MS = 1'000'000;

  // traces
  std::unordered_map<int64_t, OrderStatus> orders_;

  // x dumps

  std::vector<int64_t> dataset1_ts_;
  std::vector<double> dataset1_x_;  // cast this to 2d
  std::vector<double> dataset1_y1_;
  std::vector<double> dataset1_y2_;

  std::vector<int64_t> dataset2_ts_;
  std::vector<double> dataset2_x_;  // cast this to 2d
  std::vector<double> dataset2_y_;

  OrderLatencyFeatures features_;

  // y

  // using YWindow = MovingWindowWithCallee<PendingRow, LatencyModelDatasetDumper>;
  // YWindow order_submit_to_confirm_time_10ms_;
  // YWindow order_submit_to_confirm_time_20ms_;
  // YWindow order_submit_to_confirm_time_30ms_;
  // YWindow order_submit_to_first_fill_time_10ms_;
  // YWindow order_submit_to_first_fill_time_20ms_;
  // YWindow order_submit_to_first_fill_time_30ms_;
  // YWindow order_cancel_submit_to_confirm_time_10ms_;
  // YWindow order_cancel_submit_to_confirm_time_20ms_;
  // YWindow order_cancel_submit_to_confirm_time_30ms_;
};
